package ch.ethz.inf.vs.sunspot.driveronspot;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.peripheral.ISleepManager;
import com.sun.spot.peripheral.ITimeoutableConnection;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.peripheral.UnableToDeepSleepException;
import com.sun.spot.peripheral.radio.routing.RoutingPolicy;
import com.sun.spot.peripheral.radio.routing.RoutingPolicyManager;
import com.sun.spot.sensorboard.peripheral.ILightSensor;
import com.sun.spot.sensorboard.peripheral.ISwitch;
import com.sun.spot.sensorboard.peripheral.ITemperatureInput;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.sensorboard.peripheral.IAccelerometer3D;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import java.io.IOException;
import java.util.Random;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

/*
 * Class handling spot registration and handling code in a single file, unthreaded
 */
public class SpotDriverAdvanced extends MIDlet {

    private static final int HOST_PORT = 109;
    private static final int TARGET_SLEEP_TIME = 2000;
    ITriColorLED[] leds = EDemoBoard.getInstance().getLEDs();
    ISwitch[] switchSensor = EDemoBoard.getInstance().getSwitches();
    ILightSensor lightSensor = EDemoBoard.getInstance().getLightSensor();
    ITemperatureInput temperatureSensor = EDemoBoard.getInstance().getADCTemperature();
    IAccelerometer3D accelerationSensor = EDemoBoard.getInstance().getAccelerometer();
    
    ISleepManager sleepManager = Spot.getInstance().getSleepManager();
    private static final String MY_NAME = System.getProperty("name");
    // private static final String MY_NAME = "USERTOKEN_mayersi";
    // private static final String MY_NAME = "testname";
    
    Random generator;
    SpotRepresentation thisIsMe;
    // Connection establishment and handling vars
    String gatewayAddress;
    int synchronizationPort = 0;
    int synchronizationInterval = 0;

    /**
     * Entry point for the program
     * @throws MIDletStateChangeException
     */
    protected void startApp() throws MIDletStateChangeException {
        init();
        boolean hasConnection = false;

        while (true) {
            hasConnection = establishConnection();
            System.out.println("Establishing connection to server...Using port " + HOST_PORT);

            // Now it has a connection... so push and pull data
            // basically: synchronize data and instruction "caches"
            if (hasConnection) {
                // Loops as long as the connection to the gateway is established
                while (synchronizeWithGateway()) sleepSync();
                
                // Connection has been lost - reinitialize
                System.out.println("Synchronization Loop stopping.");
                init();
            } else {
                sleepRandom();
            }
        }
    }

    /**
     * Initializes connection variables and sleep- and routing managers to enable deepsleep
     */
    private void init() {
        gatewayAddress = "";
        synchronizationPort = 0;
        synchronizationInterval = 0;
        sleepManager.enableDeepSleep();
        sleepManager.enableDiagnosticMode();
        RoutingPolicy rp = new RoutingPolicy(RoutingPolicy.ENDNODE);
        RoutingPolicyManager.getInstance().policyHasChanged(rp);

        generator = new Random(System.currentTimeMillis());
        thisIsMe = new SpotRepresentation();
    }

    /**
     * This method does one round of synchronization with the gateway
     * @return connection still active
     */
    private boolean synchronizeWithGateway() {
        RadiogramConnection sendConn = null;
        Datagram datagram = null;

        // System.out.println("Starting HeartBeat...gateway address is " + gatewayAddress);
        boolean stillHasConnection = true;

        // Prepare data to be sent
        try {
            thisIsMe.setLightValue((short) lightSensor.getAverageValue());
            thisIsMe.setTemperatureValue((float) temperatureSensor.getCelsius());

            for (int i = 0; i < Constants.SPOT_NUMBER_AVAILABLE_LEDS; i++) {
                thisIsMe.setLedStatus(i, leds[i].isOn());
                thisIsMe.setLedColor(i, new short[]{ (short) leds[i].getRed(), (short) leds[i].getGreen(), (short) leds[i].getBlue()});
            }

            thisIsMe.setSwitchStatus(0, switchSensor[0].isClosed());
            thisIsMe.setSwitchStatus(1, switchSensor[1].isClosed());

            thisIsMe.setAccelerationValues(new float[] { ((int) (accelerationSensor.getAccelX() * 100)) / 100.0f, ((int) (accelerationSensor.getAccelY() * 100)) / 100.0f, ((int) (accelerationSensor.getAccelZ() * 100)) / 100.0f});
            thisIsMe.setTiltValues(new float[] { ((int) (accelerationSensor.getTiltX() * 100)) / 100.0f,((int) (accelerationSensor.getTiltY() * 100)) / 100.0f, ((int) (accelerationSensor.getTiltZ() * 100)) / 100.0f});
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        String sendString = MY_NAME + Constants.SEPARATOR + thisIsMe.encodeJSON_OutToHost(synchronizationInterval);
        String message = "";

        try {
            sendConn = (RadiogramConnection) Connector.open("radiogram://" + gatewayAddress + ":" + synchronizationPort);
            ((ITimeoutableConnection) sendConn).setTimeout(5000);
            datagram = sendConn.newDatagram(sendConn.getMaximumLength());
            datagram.reset();

            // Send own name to gateway
            datagram.writeUTF(sendString);
            // System.out.print("Sending " + sendString + " to gateway...");
            sendConn.send(datagram);
            // System.out.println("!");

            // System.out.println("Waiting for " + Constants.MESSAGE_SPOT_RESYNC);

            // Receiving answer from gateway
            // System.out.println("Receiving from gateway...");
            sendConn.receive(datagram);

            message = datagram.readUTF();
            // System.out.println("\tGateway says: " + message.substring(0, message.indexOf(Constants.SEPARATOR)));

            message = message.substring(message.indexOf(Constants.SEPARATOR) + 1);
            // System.out.println("\tMessage: " + message);

            sendConn.close();
        } catch (Exception e) {
            System.out.println(e.getMessage() + " in HeartBeat - stopping HeartBeat and terminating Connection");

            // If an exception is caught here, there is a problem with the gateway...so cancel the connection
            stillHasConnection = false;

            leds[7].setColor(Constants.colorEstablishingConnection);
            
            try {
                if (sendConn != null) {
                    sendConn.close();
                }
            } catch (IOException ex) {
                System.out.println("Exception caught while trying to close recvConn/sendConn in HeartBeatThread: " + ex.getMessage());                
            }
        }
       
        // Process received data
        thisIsMe.decodeJSON_InFromHost(message);
        this.synchronizationInterval = thisIsMe.getResyncTime();
        /*
        if (synchronizationInterval < Constants.SPOT_SYNC_MININTERVAL) {
                // We could switch to some kind of quick-pushing-mode... so, don't sleep anymore...
                synchronizationInterval = 0;
           }
        */
        for (int i = 0; i < Constants.SPOT_NUMBER_AVAILABLE_LEDS; i++) {
            if (thisIsMe.getLedStatusFuture(i)) {
                thisIsMe.setLedStatus(i, true);
                leds[i].setOn();
            } else {
                leds[i].setOff();
            }

            thisIsMe.setLedColor(i, thisIsMe.getLedColorFuture(i));
            leds[i].setRGB(thisIsMe.getLedColorFuture(i)[0], thisIsMe.getLedColorFuture(i)[1], thisIsMe.getLedColorFuture(i)[2]);
        }


        return stillHasConnection;
    }

    /**
     * This method tries to establish a connection to the gateway on port HOST_PORT and returns if it was successful or not
     * @return connection established
     */
    private boolean establishConnection() {
        boolean connectionEstablished = false;

        RadiogramConnection sendConn = null;
        RadiogramConnection recvConn = null;
        Datagram datagram = null;

        try {
            leds[7].setOn();

            // System.out.println("Establishing connection to server...Using port " + HOST_PORT);
            sendConn = (RadiogramConnection) Connector.open("radiogram://broadcast:" + HOST_PORT);
            ((ITimeoutableConnection) sendConn).setTimeout(2000);
            datagram = sendConn.newDatagram(sendConn.getMaximumLength());
            datagram.reset();

            /** myName could be null **/
            String myName = MY_NAME;
            if (myName == null) myName = "SPOT_NAME_NOT_SET";
            
            datagram.writeUTF(myName);

            sendConn.send(datagram);
            sendConn.close();

            recvConn = (RadiogramConnection) Connector.open("radiogram://:" + HOST_PORT);
            datagram = recvConn.newDatagram(recvConn.getMaximumLength());
            ((ITimeoutableConnection) recvConn).setTimeout(1000);

            recvConn.receive(datagram);
            recvConn.close();

            String receiveString = datagram.readUTF();

            if (receiveString.startsWith(Constants.MESSAGE_SPOT_HELO + Constants.SEPARATOR)) {
                gatewayAddress = datagram.getAddress();
                System.out.println(Constants.MESSAGE_SPOT_HELO + ", " + gatewayAddress + "!");

                System.out.println("Received: " + receiveString);
                receiveString = receiveString.substring(2);

                // No need to checkin earlier... Gateway already has Tolerance...
                synchronizationInterval = Integer.parseInt(receiveString.substring(0, receiveString.indexOf(Constants.SEPARATOR)));
                synchronizationPort = Integer.parseInt(receiveString.substring(receiveString.indexOf(Constants.SEPARATOR) + 1));

                System.out.println("Gateway requires heartbeat every " + synchronizationInterval + " on port " + synchronizationPort);
                connectionEstablished = true;

                leds[7].setColor(Constants.colorActive);
                leds[7].setOn();
            }
            else if (receiveString.startsWith(Constants.YOU_ARE_BLOCKED)) {
                System.out.println("Received: " + receiveString);
                
                leds[7].setColor(Constants.colorBlocked);
                leds[7].setOn();
            }
        } catch (Exception e) {
            System.out.println(e.getMessage() + " while establishing connection: " + e.getMessage());
            
            leds[7].setColor(Constants.colorEstablishingConnection);
            leds[7].setOn();

            try {
                // Just to be on the safe side...
                if (recvConn != null) recvConn.close();
            } catch (IOException ex) {
                System.out.println("Exception caught: " + ex.toString() + "- Not possible to close connection recvConn!");
            }
        }

        return connectionEstablished;
    }

    /**
     * This method causes the thread to sleep for a random amount of time (average: TARGET_SLEEP_TIME)
     */
    private void sleepRandom() {
        // If no connection has been established, sleep, then retry
        int sleepTime = TARGET_SLEEP_TIME + (generator.nextInt(TARGET_SLEEP_TIME) - TARGET_SLEEP_TIME / 2);

        // System.out.println("Sleeping for " + sleepTime + "ms between connection attempts...");
        try {
            leds[7].setOff();
            sleepManager.ensureDeepSleep(sleepTime);
        } catch (UnableToDeepSleepException ex) {
            leds[7].setOn();
            System.out.println("\t" + ex.getMessage());
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException ex1) {
                System.out.println("\t" + ex1.getMessage() + "(lvl 1)");
            }
        }
        // System.out.println("Waking up!");
        leds[7].setOn();
    }

    /**
     * Sleep for the interval given by synchronizationInterval
     */
    private void sleepSync() {
        leds[7].setOff();

        if (synchronizationInterval < 300) {
            // int col = generator.nextInt(2);
            LEDColor currentColor = leds[7].getColor();
            if (currentColor.equals(Constants.colorActive)) leds[7].setColor(Constants.colorActiveAndFast);
            else leds[7].setColor(Constants.colorActive);
        }

        try {
            // System.out.println("Sleeping for " + synchronizationInterval + "ms between Sync Attempts...");
            sleepManager.ensureDeepSleep(synchronizationInterval);
        } catch (UnableToDeepSleepException ex) {
            try {
                Thread.sleep(synchronizationInterval);
            } catch (InterruptedException ex1) {
                System.out.println("\t" + ex1.getMessage() + "(lvl 1)");
            }
        }

        // System.out.println("Total deep sleep time: " + sleepManager.getTotalDeepSleepTime());
        // System.out.println("Total shallow sleep time: " + sleepManager.getTotalShallowSleepTime());
        // System.out.println("Total uptime: " + sleepManager.getUpTime());

        leds[7].setOn();
    }

    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
        for (int i = 0; i < 8; i++) {
            leds[i].setOff();
        }

        // System.out.println("Total deep sleep time: " + sleepManager.getTotalDeepSleepTime());
        // System.out.println("Total shallow sleep time: " + sleepManager.getTotalShallowSleepTime());
    }

    protected void pauseApp() {
    }
}